package com.googlecode.economini.converter;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.DefaultHandler;

import com.google.common.collect.ImmutableList;

public class TokenizingHandler<T extends Enum<?>> extends DefaultHandler {

  /** {@link TokenizerNode} place holder that should be ignored */
  private final TokenizerState<T> nullState = new TokenizerState<T>(null, null);

  /** {@link Deque} with the current tokenizer stack. */
  private final Deque<TokenizerState<T>> nodes;

  /** Builder for token list. Initialized in {@link #startDocument()}. */
  private ImmutableList.Builder<Token<T>> tokenBuilder;

  public TokenizingHandler(TokenizerNode<T> root) {
    nodes = new ArrayDeque<TokenizerState<T>>();
    nodes.addFirst(new TokenizerState<T>(root, new AttributesImpl()));
  }

  public List<Token<T>> getTokens() {
    return tokenBuilder.build();
  }

  @Override
  public void characters(char[] ch, int start, int length) throws SAXException {
    TokenizerState<T> currentState = nodes.peek();
    if (currentState != nullState) {
      String value = String.valueOf(ch, start, length);
      Token<T> token = currentState.buildToken(value);

      if (token != null) {
        tokenBuilder.add(token);
      }
    }
  }

  @Override
  public void startElement(String uri, String localName, String qName,
      Attributes attributes) throws SAXException {
    TokenizerState<T> state = nodes.peekFirst();

    // nullState has nullState children
    if (state == nullState) {
      nodes.addFirst(nullState);
      return;
    }

    TokenizerNode<T> childNode = state.node.getNodeFor(qName, attributes);

    TokenizerState<T> childState = childNode != null
        ? new TokenizerState<T>(childNode, attributes)
            : nullState;

        if (childState != nullState) {
          Token<T> token = childState.buildToken(null);

          if (token != null) {
            tokenBuilder.add(token);
          }
        }

    nodes.addFirst(childState);
  }

  @Override
  public void endElement(String uri, String localName, String qName)
      throws SAXException {
    // Remove the head of the node queue.
    nodes.pollFirst();
  }

  @Override
  public void startDocument() throws SAXException {
    tokenBuilder = ImmutableList.builder();
  }

  private static class TokenizerState<T extends Enum<?>> {
    private final TokenizerNode<T> node;
    private final Attributes attributes;

    private TokenizerState(TokenizerNode<T> node, Attributes attributes) {
      this.node = node;
      this.attributes = attributes;
    }

    private Token<T> buildToken(String element) {
      return node.buildToken(element, attributes);
    }
  }
}
